/*    This file is part of nit.
 *
 *    Nit is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Nit is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with nit.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include "err.h"

static void
reset(Err *err)
{
	err->print_under = err->print_where = err->print_what =
		err->print_line = err->print_pos = err->occured = 0;
}

static void
err_print(Err *err)
{
        char buf[256];
	int space = err->print_where || err->print_line || err->print_pos;

	if (!err->occured) {
		fprintf(stderr, "No error has occured\n");
		return;
	}

	if (err->print_under) {
		/* It is useless to print this to stderr */
		for (unsigned i = 0; i < err->under; ++i)
			printf(" ");

		printf("^\n");
	}

	if (err->print_where) {
		if (err->where)
			fprintf(stderr, "%s:", err->where);
		else
			fprintf(stderr, "(Failed to allocate "
				"failure location string):");
	}

	if (err->print_line)
		fprintf(stderr, "%zu:", err->line);

	if (err->print_pos)
		fprintf(stderr, "%zu:", err->pos);

	if (space)
		fprintf(stderr, " ");


	if (err->custom) {
		fprintf(stderr, "%s", err->custom);
	} else {
		strerror_r(err->errnum, buf, sizeof(buf));
		fprintf(stderr, "%s", buf);
	}

	if (err->print_what) {
		if (err->what)
			fprintf(stderr, ": %s", err->what);
		else
			fprintf(stderr, ": (Failed to allocate "
				"failure content string)");
	}

	fprintf(stderr, "\n");
}

void
err_init(Err *err, Err_log log)
{
	err->where = err->what = NULL;
	err->where_len = err->what_len = 0;
	err->occured = 0;
	err->log = log ? log : err_print;
}

void
err_dispose(Err *err)
{
	free(err->where);
	free(err->what);
}

void
err_std(Err *err)
{
	reset(err);
	err->custom = NULL;
	err->errnum = errno;
	err->occured = 1;
}

void
err_nit(Err *err, Nit_error error)
{
	static const char *str_tbl[] = {
		[NIT_ERROR_FINE] = "No error",
		[NIT_ERROR_MEMORY] = "Ran out of memory",
		[NIT_ERROR_EMPTY] = "Data structure empty",
		[NIT_ERROR_NO_NEXT] = "Missing next value",
		[NIT_ERROR_ALREADY] = "Value already exists",
		[NIT_ERROR_NOT_FOUND] = "Value not found",
		[NIT_ERROR_NOT_DOABLE] = "Action is not doable",
		[NIT_ERROR_NOT_UNDOABLE] = "Action is not undoable",
		[NIT_ERROR_NOT_DIFF] = "Action cannot be done differently",
		[NIT_ERROR_FAILED] = "Failed to do action",
		[NIT_ERROR_ROI] = "Real action cannot be done on imaginary "
		"branch",
		[NIT_ERROR_REDO_PAST] = "Past cannot be redone for real"
	};

	err_cust(err, str_tbl[error]);
}

void
err_cust(Err *err, const char *msg)
{
	reset(err);
	err->custom = msg;
	err->occured = 1;
}

void
err_under(Err *err, size_t under)
{
	err->print_under = 1;
	err->under = under;
}

int
err_where(Err *err, const char *where)
{
	size_t new_len = strlen(where) + 1;
	size_t total_len = new_len;
	size_t old_len = 0;
	char *tmp;

	if (err->print_where)
		total_len += (old_len = strlen(err->where) + 1);

	if (total_len > err->where_len) {
	        if (!(tmp = realloc(err->where, total_len))) {
			err->where_len = 0;
			free(err->where);
			err->where = NULL;
			return -1;
		}

		err->where = tmp;
		err->where_len = total_len;
	}

	memmove(err->where + new_len, err->where, old_len);
	strcpy(err->where, where);

	if (err->print_where)
		err->where[new_len - 1] = ':';

	err->print_where = 1;
	return 0;
}

int
err_what(Err *err, const char *what)
{
	size_t what_len = strlen(what) + 1;
	char *tmp;

	err->print_what = 1;

	if (what_len > err->what_len) {
	        if (!(tmp = realloc(err->what, what_len))) {
			err->what_len = 0;
			free(err->what);
			err->what = NULL;
			return -1;
		}

		err->what = tmp;
		err->what_len = what_len;
	}

	strcpy(err->what, what);
	return 0;
}

void
err_line(Err *err, size_t line)
{
	err->print_line = 1;
	err->line = line;
}

void
err_pos(Err *err, size_t pos)
{
	err->print_pos = 1;
	err->pos = pos;
}

void
err_log(Err *err)
{
	err->log(err);
}
